package cn.shengchao.examstar.ai.infrastructure.persistence.repository;

import cn.shengchao.examstar.ai.domain.model.StudyPlan;
import cn.shengchao.examstar.ai.domain.model.studyplan.StudyPlanId;
import cn.shengchao.examstar.ai.domain.repository.IAiStudyPlanRepository;
import cn.shengchao.examstar.ai.domain.valueobject.*;
import cn.shengchao.examstar.ai.infrastructure.dao.AiStudyPlanDao;
import cn.shengchao.examstar.ai.infrastructure.dao.po.AiStudyPlan;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * AI学习计划仓储实现
 * 实现领域仓储接口，处理领域对象与持久化对象的转换
 */
@Repository
public class AiStudyPlanRepository implements IAiStudyPlanRepository {

    private static final Logger logger = LoggerFactory.getLogger(AiStudyPlanRepository.class);

    @Autowired
    private AiStudyPlanDao aiStudyPlanDao;

    // ========== 领域仓储接口实现 ==========

    @Override
    public StudyPlan save(StudyPlan studyPlan) {
        logger.debug("保存学习计划: {}", studyPlan.getId());
        AiStudyPlan po = convertToPO(studyPlan);

        if (po.getId() == null) {
            aiStudyPlanDao.insert(po);
            logger.debug("新增学习计划成功，ID: {}", po.getId());
        } else {
            aiStudyPlanDao.updateById(po);
            logger.debug("更新学习计划成功，ID: {}", po.getId());
        }

        return convertToDomain(po);
    }

    @Override
    public Optional<StudyPlan> findById(StudyPlanId id) {
        if (id == null) {
            return Optional.empty();
        }

        AiStudyPlan po = aiStudyPlanDao.selectById(id.getValue());
        return po != null ? Optional.of(convertToDomain(po)) : Optional.empty();
    }

    @Override
    public Optional<StudyPlan> findByUserIdAndSpecificationType(UserId userId, SpecificationType specificationType) {
        if (userId == null || specificationType == null) {
            return Optional.empty();
        }

        AiStudyPlan po = aiStudyPlanDao.selectOne(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
                        .eq(AiStudyPlan::getSpecificationType, specificationType.getValue())
                        .last("LIMIT 1")
        );

        return po != null ? Optional.of(convertToDomain(po)) : Optional.empty();
    }

    @Override
    public List<StudyPlan> findByUserId(UserId userId) {
        if (userId == null) {
            return List.of();
        }

        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
                        .orderByDesc(AiStudyPlan::getId)
        );

        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public List<StudyPlan> findInProgressPlansByUserId(UserId userId) {
        if (userId == null) {
            return List.of();
        }

        Date now = new Date();
        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
                        .le(AiStudyPlan::getBeginTime, now)
                        .ge(AiStudyPlan::getEndTime, now)
        );

        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public List<StudyPlan> findInProgressPlansBySpecificationType(SpecificationType specificationType) {
        if (specificationType == null) {
            return List.of();
        }

        Date now = new Date();
        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getSpecificationType, specificationType.getValue())
                        .le(AiStudyPlan::getBeginTime, now)
                        .ge(AiStudyPlan::getEndTime, now)
        );

        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public void delete(StudyPlan studyPlan) {
        if (studyPlan != null && studyPlan.getId() != null) {
            logger.debug("删除学习计划: {}", studyPlan.getId());
            aiStudyPlanDao.deleteById(studyPlan.getId().getValue());
        }
    }

    @Override
    public void deleteById(StudyPlanId id) {
        if (id != null) {
            logger.debug("根据ID删除学习计划: {}", id);
            aiStudyPlanDao.deleteById(id.getValue());
        }
    }

    @Override
    public boolean existsByUserIdAndSpecificationType(UserId userId, SpecificationType specificationType) {
        if (userId == null || specificationType == null) {
            return false;
        }

        Long count = aiStudyPlanDao.selectCount(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
                        .eq(AiStudyPlan::getSpecificationType, specificationType.getValue())
        );

        return count != null && count > 0;
    }

    @Override
    public boolean existsBySpecificationType(SpecificationType specificationType) {
        if (specificationType == null) {
            return false;
        }

        Long count = aiStudyPlanDao.selectCount(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getSpecificationType, specificationType.getValue())
        );

        return count != null && count > 0;
    }

    @Override
    public List<StudyPlan> findByUserIdAndStatus(UserId userId, StudyPlanStatus status) {
        if (userId == null || status == null) {
            return List.of();
        }

        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
                        .eq(AiStudyPlan::getStatus, status.name())
        );

        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public List<StudyPlan> findByStatus(StudyPlanStatus status) {
        if (status == null) {
            return List.of();
        }

        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getStatus, status.name())
        );

        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public boolean existsActiveByUserIdAndSpecificationType(UserId userId, SpecificationType specificationType) {
        if (userId == null || specificationType == null) {
            return false;
        }

        Long count = aiStudyPlanDao.selectCount(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
                        .eq(AiStudyPlan::getSpecificationType, specificationType.getValue())
                        .eq(AiStudyPlan::getStatus, StudyPlanStatus.ACTIVE.name())
        );

        return count != null && count > 0;
    }

    @Override
    public long countByUserId(UserId userId) {
        if (userId == null) {
            return 0L;
        }

        return aiStudyPlanDao.selectCount(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
        );
    }

    @Override
    public long countByStatus(StudyPlanStatus status) {
        if (status == null) {
            return 0L;
        }

        return aiStudyPlanDao.selectCount(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getStatus, status.name())
        );
    }

    @Override
    public List<StudyPlan> findByUserIdWithPagination(UserId userId, int page, int size) {
        if (userId == null) {
            return List.of();
        }
        Page<AiStudyPlan> pageRequest = new Page<>(page, size);
        IPage<AiStudyPlan> pagedResult = aiStudyPlanDao.selectPage(pageRequest,
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getUserId, userId.getValue())
        );

        return pagedResult.getRecords().stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public List<StudyPlan> findExpiringPlans(int days) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime expirationDate = now.plusDays(days);
        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getStatus, StudyPlanStatus.ACTIVE.name())
                        .le(AiStudyPlan::getEndTime, localDateTimeToDate(expirationDate))
        );

        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public List<StudyPlan> findStagnantPlans(int days) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime stagnantDate = now.minusDays(days);
        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getStatus, StudyPlanStatus.ACTIVE.name())
                        .le(AiStudyPlan::getUpdateTime, localDateTimeToDate(stagnantDate))
        );
        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    @Override
    public List<StudyPlan> findBySpecificationType(SpecificationType specificationType) {
        if (specificationType == null) {
            return List.of();
        }
        List<AiStudyPlan> pos = aiStudyPlanDao.selectList(
                Wrappers.lambdaQuery(AiStudyPlan.class)
                        .eq(AiStudyPlan::getSpecificationType, specificationType.getValue())
        );
        return pos.stream()
                .map(this::convertToDomain)
                .collect(Collectors.toList());
    }

    private AiStudyPlan convertToPO(StudyPlan studyPlan) {
        if (studyPlan == null) return null;
        AiStudyPlan po = new AiStudyPlan();
        po.setId(studyPlan.getId() != null ? studyPlan.getId().getValue() : null);
        po.setUserId(studyPlan.getUserId().getValue());
        po.setSpecificationType(studyPlan.getSpecificationType().getValue());
        po.setBeginTime(localDateTimeToDate(studyPlan.getStudyPeriod().getStartTime()));
        po.setEndTime(localDateTimeToDate(studyPlan.getStudyPeriod().getEndTime()));
        po.setStatus(studyPlan.getStatus().name());
        po.setDegreeOfMastery(studyPlan.getDegreeOfMastery().getCode());
        po.setDayTime(studyPlan.getDailyStudyDuration().getMinutes());
        po.setLearningSuggestions(studyPlan.getStudyAdvice());
        // createTime and updateTime are handled by MyBatis-Plus
        return po;
    }

    private StudyPlan convertToDomain(AiStudyPlan po) {
        if (po == null) return null;
        return StudyPlan.fromPersistence(
                StudyPlanId.of(po.getId()),
                SpecificationType.of(po.getSpecificationType()),
                UserId.of(po.getUserId()),
                DegreeOfMastery.fromCode(po.getDegreeOfMastery()),
                StudyDuration.ofMinutes(po.getDayTime()),
                StudyPeriod.of(dateToLocalDateTime(po.getBeginTime()), dateToLocalDateTime(po.getEndTime())),
                po.getLearningSuggestions(),
                StudyPlanStatus.valueOf(po.getStatus()),
                dateToLocalDateTime(po.getCreateTime()),
                dateToLocalDateTime(po.getUpdateTime())
        );
    }

    private Date localDateTimeToDate(LocalDateTime localDateTime) {
        return localDateTime != null ? Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()) : null;
    }

    private LocalDateTime dateToLocalDateTime(Date date) {
        return date != null ? date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() : null;
    }
} 